using System;
using Furiant.Figment.Agents.Inventory;
using Furiant.Figment.Core;

namespace Furiant.Figment.Items.Lootable
{
    /// <summary>
    /// Represents a GameObject which can be picked up and carried by an IHasInventory implementor.
    /// </summary>
    public class Loot : GameObject, ILootable
    {
        #region Fields

        private InventoryBase _currentInventory;

        private readonly string _nameSingle;
        private readonly string _nameGrouped;
        private readonly string _description;
        private readonly int _unitMass;
        private readonly int _unitsPerGroup;
        private int _unitCount = 1;

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of Loot.
        /// </summary>
        /// <param name="nameSingle">The player-readable name of a single unit of this instance.</param>
        /// <param name="nameGrouped">The player-readable name of a group of this instance. </param>
        /// <param name="description">The player-readable description of this instance.</param>
        /// <param name="unitMass">The base mass of a single unit of this instance.</param>
        /// <param name="unitsPerGroup">The maximum number of units of this instance that will be treated as a single group.</param>
        /// <param name="unitCount">The number of units this instance represents when created.</param>
        protected Loot(
            string nameSingle,
            string nameGrouped,
            string description,
            int unitMass,
            int unitsPerGroup,
            int unitCount)
        {
            _nameSingle = nameSingle;
            _nameGrouped = nameGrouped;
            _description = description;
            _unitMass = Math.Max(0, unitMass);
            _unitsPerGroup = Math.Max(1, unitsPerGroup);
            _unitCount = Math.Max(1, unitCount);
        }

        /// <summary>
        /// Creates a new instance of Loot with default values.
        /// </summary>
        /// <param name="nameSingle">The player-readable name of a single unit of this instance.</param>
        /// <param name="description">The player-readable description of this instance. </param>
        protected Loot(string nameSingle, string description) : this(nameSingle, null, description, 0, 1, 1) {}

        #endregion

        #region Properties

        /// <summary>
        /// Gets the player-readable name of a single unit of this instance.
        /// </summary>
        public virtual string NameSingle { get { return string.IsNullOrWhiteSpace(_nameSingle) ? "No name defined" : _nameSingle; } }

        /// <summary>
        /// Gets the player-readable name of a group of this instance. If null, empty, or whitespace, returns the value of NameSingle.
        /// </summary>
        public virtual string NameGrouped { get { return string.IsNullOrWhiteSpace(_nameGrouped) ? _nameSingle : _nameGrouped; } }

        /// <summary>
        /// Gets a player-readable name for this instance based on its UnitCount value.
        /// </summary>
        public virtual string Name { get { return _unitCount > 1 ? NameGrouped : _nameSingle; } }

        /// <summary>
        /// The player-readable description of this instance.
        /// </summary>
        public virtual string Description { get { return _description; } }

        /// <summary>
        /// The Inventory to which this instance is currently assigned.
        /// </summary>
        public InventoryBase CurrentInventory
        {
            get { return _currentInventory; }
            set
            {
                var previousInventory = CurrentInventory;
                _currentInventory = value;
                OnInventoryChanged(new InventoryChangedArgs(previousInventory));
            }
        }

        IHasInventory CurrentOwner { get { return _currentInventory.Owner; } }

        /// <summary>
        /// The number of units in the group represented by this instance. This value is clamped to between 1 and the value of UnitCountMax.
        /// </summary>
        public virtual int UnitCount
        {
            get { return _unitCount; } 
            set
            {
                var previousValue = UnitCount;
                _unitCount = value;
                OnUnitCountChanged(new UnitCountChangedArgs(previousValue));
            }
        }

        /// <summary>
        /// The base mass of a single unit of this instance. This value is clamped to 0 or greater.
        /// </summary>
        public virtual int UnitMass { get { return _unitMass; } }

        /// <summary>
        /// The product of this instance's UnitMass multiplied by its Units.
        /// </summary>
        public virtual int MassTotal { get { return UnitMass * UnitCount; } }

        /// <summary>
        /// The maximum number of units of this instance which will be treated as a single group.
        /// </summary>
        public virtual int UnitsPerGroup { get { return _unitsPerGroup; } }

        #endregion

        #region Methods

        public T GetClone<T>() where T : ILootable
        {
            return (T)MemberwiseClone();
        }

        public override string ToString()
        {
            return string.Format("[{0}] ({1}) {2}", String.Format("{0:0000000000}", Id), String.Format("{0:000}", UnitCount), Name);
        }

        #endregion

        #region Events

        public event EventHandler<InventoryChangedArgs> InventoryChanged;
        protected virtual void OnInventoryChanged(InventoryChangedArgs e)
        {
            var handler = InventoryChanged;
            if (handler != null) handler(this, e);
        }

        public event EventHandler<UnitCountChangedArgs> UnitCountChanged;
        protected virtual void OnUnitCountChanged(UnitCountChangedArgs e)
        {
            var handler = UnitCountChanged;
            if (handler != null) handler(this, e);
        }

        #endregion
    }

    public class InventoryChangedArgs : EventArgs
    {
        private readonly IInventory _previousInventory;

        public InventoryChangedArgs(IInventory previousInventory)
        {
            _previousInventory = previousInventory;
        }

        public IInventory PreviousInventory { get { return _previousInventory; } }
    }
}
